package cloudresource

import (
	"fmt"
	"net/http"

	"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
	"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/sdkerr"
	ecs "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ecs/v2"
	ecsmodel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ecs/v2/model"
	"scase.io/application-auto-scaling-service/pkg/api/model"
	"scase.io/application-auto-scaling-service/pkg/common"
	"scase.io/application-auto-scaling-service/pkg/db"
	"scase.io/application-auto-scaling-service/pkg/setting"
	"scase.io/application-auto-scaling-service/pkg/utils/logger"
)

const defaultEipChargeMode = "PRE_PAID"

type EcsResourceController struct {
	ecsClient *ecs.EcsClient
	projectId string
}

func (c *EcsResourceController) CreatePrePaidEcs(tLogger *logger.FMLogger, param *model.CreateEcsReqModel) (*ecsmodel.CreateServersResponse, error) {
	request := &ecsmodel.CreateServersRequest{}
	listServerTagsServer := getServerTag(param)
	rootVolumeServer, listDataVolumesServer := setVolume(param.Disks)
	publicipServer := &ecsmodel.PrePaidServerPublicip{Eip: getEip(param.Eip)}
	listNicsServer := []ecsmodel.PrePaidServerNic{{SubnetId: param.SubnetId}}
	metadataMap := make(map[string]string)
	metadataMap["agency_name"] = param.AgencyName
	if setting.GetDefaulHostProtect() {
		metadataMap["__support_agent_list"] = setting.GetDefaultHostProtectValue()
	}

	securityGroup := getSecurityGroup(param.SecurityGroups)
	ecsCount := int32(param.VmNum)
	isAutoRename := false
	batchCreateInMultiAz := true
	serverbody := &ecsmodel.PrePaidServer{
		ImageRef:             param.ImageID,
		FlavorRef:            param.FlavorId,
		AvailabilityZone:     &param.AvailableZone,
		Name:                 param.Name,
		Count:                &ecsCount,
		IsAutoRename:         &isAutoRename,
		KeyName:              &param.KeyName,
		Vpcid:                param.VpcId,
		Nics:                 listNicsServer,
		Publicip:             publicipServer,
		RootVolume:           rootVolumeServer,
		DataVolumes:          &listDataVolumesServer,
		ServerTags:           &listServerTagsServer,
		SecurityGroups:       &securityGroup,
		BatchCreateInMultiAz: &batchCreateInMultiAz,
		UserData:             genUserData(param.FleetId, param.EcsGroupId),
		Extendparam: &ecsmodel.PrePaidServerExtendParam{
			EnterpriseProjectId: &param.EnterpriseProjectId,
		},
		Metadata: metadataMap,
	}
	request.Body = &ecsmodel.CreateServersRequestBody{
		Server: serverbody,
	}

	response, err := c.ecsClient.CreateServers(request)
	if err != nil {
		return nil, err
	}
	return response, nil
}

func (c *EcsResourceController) GetSubJobsIdsAndServerIds(showJobResp *ecsmodel.ShowJobResponse) (map[string]string, error) {
	// 刚创建时查询子任务会为空
	if showJobResp.Entities == nil || showJobResp.Entities.SubJobs == nil {
		return nil, fmt.Errorf("subjob of jobId [%s] is empty, please wait", *showJobResp.JobId)
	}
	SubJobs := showJobResp.Entities.SubJobs

	subJobIds := make(map[string]string)
	for _, subjob := range *SubJobs {
		if subjob.Entities != nil && subjob.Entities.ServerId != nil {
			subJobId := *subjob.JobId
			serverId := *subjob.Entities.ServerId
			subJobIds[subJobId] = serverId
		}
	}
	return subJobIds, nil
}

func (c *EcsResourceController) ShowJob(jobId string) (*ecsmodel.ShowJobResponse, error) {
	request := &ecsmodel.ShowJobRequest{JobId: jobId}
	response, err := c.ecsClient.ShowJob(request)

	if err != nil {
		return nil, err
	}
	return response, nil
}

// 幂等接口，已存在则返回创建成功，key相同会覆盖原有key
func (c *EcsResourceController) UpdateTag(tags []model.InstanceTag) error {
	request := &ecsmodel.BatchCreateServerTagsRequest{}
	var listTagsBody []ecsmodel.ServerTag
	for _, tag := range tags {
		listTagsBody = append(listTagsBody, ecsmodel.ServerTag{
			Key:   tag.Key,
			Value: tag.Value,
		})
	}
	request.Body = &ecsmodel.BatchCreateServerTagsRequestBody{
		Tags:   listTagsBody,
		Action: ecsmodel.GetBatchCreateServerTagsRequestBodyActionEnum().CREATE,
	}
	response, err := c.ecsClient.BatchCreateServerTags(request)
	if err != nil {
		return err
	}
	if response.HttpStatusCode < http.StatusOK || response.HttpStatusCode >= http.StatusBadRequest {
		return fmt.Errorf("create instance tag but get an error http code:%d", response.HttpStatusCode)
	}
	return nil
}

// DeleteVm ...
func (c *EcsResourceController) BatchDeleteVm(log *logger.FMLogger, instanceIds []string) (string, error) {
	// 删除虚机
	if len(instanceIds) == 0 {
		log.Info("there are no instance need to be deleted, doing nothing")
		return "", nil
	}
	var deleteEIP = true
	var deleteVol = true
	var serverIds []ecsmodel.ServerId
	for _, instanceId := range instanceIds {
		serverIds = append(serverIds, ecsmodel.ServerId{Id: instanceId})
	}
	delReq := &ecsmodel.DeleteServersRequest{
		Body: &ecsmodel.DeleteServersRequestBody{
			DeletePublicip: &deleteEIP,
			DeleteVolume:   &deleteVol,
			Servers:        serverIds,
		},
	}
	deleteInstanceResp, err := c.ecsClient.DeleteServers(delReq)
	if err != nil {
		// vm已删除，直接返回
		respErr, ok := err.(*sdkerr.ServiceResponseError)
		if ok && (respErr.StatusCode == http.StatusNotFound) {
			log.Info("servers have been removed or not exist")
			return "", nil
		}
		log.Error("Delete servers with err: %+v", err)
		return "", err
	}

	jobId := *deleteInstanceResp.JobId
	log.Info(" delete Servers[%v] job [%s] start successfully", instanceIds, jobId)
	return jobId, nil
}

func (c *EcsResourceController) GetAvailableZones() ([]string, error) {
	response, err := c.ecsClient.NovaListAvailabilityZones(&ecsmodel.NovaListAvailabilityZonesRequest{})
	if err != nil {
		return nil, err
	}
	var azList []string
	for _, az := range *response.AvailabilityZoneInfo {
		azList = append(azList, az.ZoneName)
	}
	return azList, nil
}

func (c *EcsResourceController) ListServerDetail(request *ecsmodel.ListServersDetailsRequest) (*ecsmodel.ListServersDetailsResponse, error) {
	resp, err := c.ecsClient.ListServersDetails(request)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (c *EcsResourceController) ShowServer(serverId string) (*ecsmodel.ShowServerResponse, error) {
	request := &ecsmodel.ShowServerRequest{}
	request.ServerId = serverId
	response, err := c.ecsClient.ShowServer(request)
	if err != nil {
		return nil, err
	}
	return response, nil
}

func GetEcsController(oriProjectId string) (*EcsResourceController, error) {
	agencyInfo, err := db.GetAgencyInfo(oriProjectId)
	if err != nil {
		return nil, err
	}
	controller, err := newEcsController(agencyInfo.ProjectId, agencyInfo.AccessKey, agencyInfo.SecretAccessKey)
	if err != nil {
		return nil, err
	}
	return controller, nil
}

// 新建controller，注册ecsclient
func newEcsController(projectId string, accessKey string, secretAccessKey string) (*EcsResourceController, error) {
	cred := basic.NewCredentialsBuilder().
		WithAk(accessKey).
		WithSk(secretAccessKey).
		WithProjectId(projectId).
		Build()

	return &EcsResourceController{
		ecsClient: newEcsClient(cred, projectId),
		projectId: projectId,
	}, nil
}

func getServerTag(param *model.CreateEcsReqModel) []ecsmodel.PrePaidServerTag {
	tags := []ecsmodel.PrePaidServerTag{}
	for _, tag := range param.InstanceTags {
		tags = append(tags, ecsmodel.PrePaidServerTag{
			Key:   tag.Key,
			Value: tag.Value,
		})
	}
	return tags
}

func setVolume(disks []model.Disk) (*ecsmodel.PrePaidServerRootVolume, []ecsmodel.PrePaidServerDataVolume) {
	rootVolume := &ecsmodel.PrePaidServerRootVolume{}
	dataVolume := []ecsmodel.PrePaidServerDataVolume{}
	for _, d := range disks {
		if *d.DiskType == common.DiskTypeSYS {
			rootVolume.Size = d.Size
			rootVolume.Volumetype = getEcsDiskRootVolumeType(*d.VolumeType)
		} else {
			dataVolume = append(dataVolume, ecsmodel.PrePaidServerDataVolume{
				Volumetype: getEcsDiskDataVolumeType(*d.VolumeType),
				Size:       *d.Size,
			})
		}
	}
	return rootVolume, dataVolume
}

func getEcsDiskRootVolumeType(volumeType string) ecsmodel.PrePaidServerRootVolumeVolumetype {
	volumeTypeEnum := ecsmodel.GetPrePaidServerRootVolumeVolumetypeEnum()
	switch volumeType {
	default:
		return volumeTypeEnum.SATA
	case volumeTypeSAS:
		return volumeTypeEnum.SAS
	case volumeTypeSSD:
		return volumeTypeEnum.SSD
	case volumeTypeGPSSD:
		return volumeTypeEnum.GPSSD
	case volumeTypeCOP1:
		return volumeTypeEnum.CO_P1
	case volumeTypeUHL1:
		return volumeTypeEnum.UH_L1
	case volumeTypeESSD:
		return volumeTypeEnum.ESSD
	}
}

func getEcsDiskDataVolumeType(volumeType string) ecsmodel.PrePaidServerDataVolumeVolumetype {
	volumeTypeEnum := ecsmodel.GetPrePaidServerDataVolumeVolumetypeEnum()
	switch volumeType {
	default:
		return volumeTypeEnum.SATA
	case volumeTypeSAS:
		return volumeTypeEnum.SAS
	case volumeTypeSSD:
		return volumeTypeEnum.SSD
	case volumeTypeGPSSD:
		return volumeTypeEnum.GPSSD
	case volumeTypeCOP1:
		return volumeTypeEnum.CO_P1
	case volumeTypeUHL1:
		return volumeTypeEnum.UH_L1
	case volumeTypeESSD:
		return volumeTypeEnum.ESSD
	}
}

func getEip(eip model.Eip) *ecsmodel.PrePaidServerEip {
	bandwidth := ecsmodel.PrePaidServerEipBandwidth{
		Size:       eip.Bandwidth.Size,
		Sharetype:  getBandwidthShareType(*eip.Bandwidth.ShareType),
		Chargemode: eip.Bandwidth.ChargingMode,
		Id:         eip.Bandwidth.Id,
	}

	chargingModeExtendparam := ecsmodel.PrePaidServerEipExtendParamChargingMode{}
	chargeMode := eip.EipChargeMode
	switch chargeMode {
	default:
		chargingModeExtendparam = ecsmodel.GetPrePaidServerEipExtendParamChargingModeEnum().PRE_PAID
	case "PRE_PAID":
		chargingModeExtendparam = ecsmodel.GetPrePaidServerEipExtendParamChargingModeEnum().PRE_PAID
	case "POST_PAID":
		chargingModeExtendparam = ecsmodel.GetPrePaidServerEipExtendParamChargingModeEnum().POST_PAID
	}
	extendparamEip := &ecsmodel.PrePaidServerEipExtendParam{
		ChargingMode: &chargingModeExtendparam,
	}

	return &ecsmodel.PrePaidServerEip{
		Iptype:      *eip.IpType,
		Bandwidth:   &bandwidth,
		Extendparam: extendparamEip,
	}
}

func getBandwidthShareType(shareType string) ecsmodel.PrePaidServerEipBandwidthSharetype {
	shareTypeEnum := ecsmodel.GetPrePaidServerEipBandwidthSharetypeEnum()
	switch shareType {
	case shareTypeWhole:
		return shareTypeEnum.WHOLE
	case shareTypePer:
		return shareTypeEnum.PER
	default:
		return shareTypeEnum.PER
	}
}

func getSecurityGroup(securityGroup []model.SecurityGroup) []ecsmodel.PrePaidServerSecurityGroup {
	sgList := []ecsmodel.PrePaidServerSecurityGroup{}
	for _, sg := range securityGroup {
		sgList = append(sgList, ecsmodel.PrePaidServerSecurityGroup{
			Id: sg.Id,
		})
	}
	return sgList
}
